﻿//
// $HeadURL: http://codeka-memcache.googlecode.com/svn/trunk/Codeka.Memcache/Commands/StoreCommand.cs $
// $Revision: 10 $
// $Author: dean.codeka $     $Date: 2010-02-21 06:05:46 +0000 (Sun, 21 Feb 2010) $
//
// Copyright © 2009-2010 Dean Harding, all rights reserved.
//

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Net;

namespace Codeka.Memcache.Commands
{
    /// <summary>
    /// The <see cref="StoreCommand"/> is used to store value(s) in the cache, "add",
    /// "set", "replace" all go through here.
    /// </summary>
    public class StoreCommand : Command
    {
        private List<KeyValuePair<string, StoreObject>> storeObjects;

        public StoreCommand(params KeyValuePair<string, StoreObject>[] storeObjects)
        {
            this.storeObjects = new List<KeyValuePair<string, StoreObject>>(storeObjects);
            this.Results = new Dictionary<string, StoreResult>();
        }

        /// <summary>
        /// Gets or sets the <see cref="StoreResult"/> from this store operation.
        /// </summary>
        public IDictionary<string, StoreResult> Results { get; set; }

        /// <summary>
        /// Get an <see cref="IEnumerable{T}"/> collection of <see cref="Packet"/>s that
        /// we want to send to the server.
        /// </summary>
        public override IEnumerable<Packet> GetPackets()
        {
            for (int i = 0; i < storeObjects.Count; i++)
            {
                var key = storeObjects[i].Key;
                var storeObject = storeObjects[i].Value;

                yield return new Packet {
                    Header = {
                        PacketType = PacketType.Request,
                        OpCode = GetOpCode(storeObject.StoreMode),
                        // note: the rest are filled out when the header is serialized
                    },
                    Key = key,
                    Value = SerializeValue(storeObject),
                    Extra = SerializeExtra(storeObject),
                };
            }
        }

        /// <summary>
        /// This is called when we get a response from the server.
        /// </summary>
        public override bool ProcessResponse(Packet pkt)
        {
            StoreResult storeResult;
            switch (pkt.Header.StatusCode)
            {
                case StatusCodes.KeyExists:
                    storeResult = StoreResult.ItemAlreadyExists;
                    break;
                case StatusCodes.KeyNotFound:
                    storeResult = StoreResult.ItemDoesNotExist;
                    break;
                case StatusCodes.ItemNotStored:
                    storeResult = StoreResult.CasValueDifferent;
                    break;
                case StatusCodes.NoError:
                    storeResult = StoreResult.Success;
                    break;
                default:
                    storeResult = StoreResult.UnknownError;
                    break;
            }

            // resposes are returned in the order we sent the requests so we just
            // match up by how many we've got so far....
            int index = Results.Count;

            // we shouldn't ever get this!!
            if (index >= storeObjects.Count)
                return false;

            Results.Add(storeObjects[index].Key, storeResult);

            // if this was the last response, then we're done!
            return (index < storeObjects.Count - 1);
        }

        /// <summary>
        /// Serialises the "Extra" field of the packet for the given <see cref="StoreObject"/>.
        /// </summary>
        private byte[] SerializeExtra(StoreObject storedObject)
        {
            using (var ms = new MemoryStream())
            {
                var writer = new BinaryWriter(ms);
                writer.Write(IPAddress.HostToNetworkOrder(storedObject.Flags));
                writer.Write(IPAddress.HostToNetworkOrder((int)storedObject.Expiry.TotalSeconds));
                writer.Flush();
                return ms.ToArray();
            }
        }

        /// <summary>
        /// Serializes the given value into a byte array, for storage.
        /// </summary>
        private byte[] SerializeValue(StoreObject storedObject)
        {
            // todo: make this pluggable
            using (var ms = new MemoryStream())
            {
                var fmt = new BinaryFormatter();
                fmt.Serialize(ms, storedObject.Value);
                return ms.ToArray();
            }
        }

        /// <summary>
        /// Gets the <see cref="PacketOpCode"/> for this command.
        /// </summary>
        /// <param name="storeMode">The <see cref="StoreMode"/> we're translating.</param>
        private PacketOpCode GetOpCode(StoreMode storeMode)
        {
            switch (storeMode)
            {
                case StoreMode.Add:
                    return PacketOpCode.Add;
                case StoreMode.Replace:
                    return PacketOpCode.Replace;
                case StoreMode.Set:
                    return PacketOpCode.Set;
            }

            throw new ArgumentException("Unknown StoreMode: " + storeMode, "store"+"Mode");
        }
    }
}
